<?php

namespace Yolo\Scheduler;

use Exception;
use Yolo\Log\Log;
use Yolo\Utils\DateTime;
use Swoole\Coroutine;

abstract class AbstractScheduler
{
    /**
     * @var string 计划任务名称（需唯一）
     */
    protected string $name = '';

    /**
     * @var bool 是否立即执行一次
     */
    protected bool $rightNow = false;

    /**
     * @var int 计划任务间隔时间（秒）
     */
    protected int $interval = 3600;

    /**
     * @var string 计划任务每天固定执行时间（格式：H:i，如果存在此值，则忽略$interval的值）
     */
    protected string $fixTime = '';

    /**
     * 计划任务实现
     * @return int
     */
    abstract public function handle(): int;

    public function getName(): string
    {
        return $this->name;
    }

    /**
     * 计划任务入口
     * @param bool $firstRun
     * @return void
     */
    public function start(bool $firstRun = true): void
    {
        try {

            if ($firstRun && $this->rightNow) {
                $result = $this->handle();
                if ($result !== 0) {
                    throw new Exception('计划任务执行失败', $result);
                }

                Log::info([
                    'action' => 'run_task',
                    'message' => 'ok',
                    'data' => [
                        'name' => $this->name,
                    ]
                ]);
            }

            while (true) {
                Coroutine::sleep($this->getNextTimeSeconds());
                $result = $this->handle();
                if ($result !== 0) {
                    throw new Exception('计划任务执行失败', $result);
                }

                Log::info([
                    'action' => 'run_task',
                    'message' => 'ok',
                    'data' => [
                        'name' => $this->name,
                    ]
                ]);
            }

        } catch (Exception $e) {

            Log::error([
                'action' => 'run_task_failed',
                'message' => $e->getMessage(),
                'data' => [
                    'name' => $this->name,
                ]
            ]);

            $this->start(false);
        }
    }

    /**
     * 获取下次剩余执行时间（秒）
     *
     * 如果是每天固定时间执行的任务，当天还未到此时间，则返回今天这个时间点的剩余秒数
     * @return int
     */
    protected function getNextTimeSeconds(): int
    {
        if ($this->fixTime) {

            $today = strtotime(date('Y-m-d') . ' ' . $this->fixTime);
            $now = time();
            if ($today > $now) {
                return $today - $now;
            }

            return $today + DateTime::SECONDS_IN_DAY - time();
        } else {

            return $this->interval;
        }
    }
}
